/*
 * Copyright (C) 2008 Rob Cash
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * WizardDialog.java
 *
 * Created on Dec 19, 2008, 4:38:32 PM
 */

package org.robcash.commons.swing.wizard.impl;

import java.awt.CardLayout;
import java.awt.Component;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.robcash.commons.swing.wizard.Wizard;
import org.robcash.commons.swing.wizard.WizardPanel;
import org.robcash.commons.swing.wizard.event.WizardModelChangeListener;
import org.robcash.commons.swing.wizard.event.WizardModelEvent;
import org.robcash.commons.swing.wizard.model.DefaultWizardModel;
import org.robcash.commons.swing.wizard.model.WizardModel;

/**
 *
 * @author Rob
 */
public class WizardDialog extends javax.swing.JDialog implements Wizard, ChangeListener, WizardModelChangeListener {

    public static final String PROP_CANCELBUTTONTEXT = "cancelButtonText";
    public static final String PROP_CANCELBUTTONICON = "cancelButtonIcon";
    public static final String PROP_PREVBUTTONTEXT = "prevButtonText";
    public static final String PROP_PREVBUTTONICON = "prevButtonIcon";
    public static final String PROP_NEXTBUTTONTEXT = "nextButtonText";
    public static final String PROP_NEXTBUTTONICON = "nextButtonIcon";
    public static final String PROP_FINISHBUTTONTEXT = "finishButtonText";
    public static final String PROP_FINISHBUTTONICON = "finishButtonIcon";
    private static final String DEFAULT_UI_TEXT_BUNDLE =
            "org/robcash/commons/swing/wizard/resources/wizard";
    
    protected ResourceBundle uiText;
    private Map<String, String> buttonText;
    private Map<String, Icon> buttonIcons;
    protected PropertyChangeSupport propertyChangeSupport;

    /**
     * Wizard model
     */
    private WizardModel model;
    
    /**
     * Return status
     */
    private int returnStatus = -1;

    //=========================================================================
    // CONSTRUCTORS
    //=========================================================================

    /** Creates new WizardDialog
     * @param parent Parent
     */
    public WizardDialog(java.awt.Frame parent) {
        this(parent, new DefaultWizardModel(), ResourceBundle.getBundle(
                DEFAULT_UI_TEXT_BUNDLE));
    }

    /** Creates new WizardDialog
     * @param parent Parent
     * @param uiText Resource bundle with button text and button icon info
     */
    public WizardDialog(java.awt.Frame parent, ResourceBundle uiText) {
        this(parent, new DefaultWizardModel(), uiText);
    }

    /** Creates new WizardDialog
     * @param parent Parent
     * @param model Wizard model
     */
    public WizardDialog(java.awt.Frame parent, WizardModel model) {
        this(parent, model, ResourceBundle.getBundle(DEFAULT_UI_TEXT_BUNDLE));
    }

    /** Creates new WizardDialog
     * @param parent Parent
     * @param model Wizard model
     * @param uiText Resource bundle with button text and button icon info
     */
    public WizardDialog(java.awt.Frame parent, WizardModel model, ResourceBundle uiText) {
        super(parent);
        this.model = model;
        model.addWizardModelChangeListener(this);
        this.uiText = uiText;
        this.buttonText = new HashMap<String, String>();
        this.buttonIcons = new HashMap<String, Icon>();
        this.propertyChangeSupport = new PropertyChangeSupport(this);
        initLabel(PROP_CANCELBUTTONTEXT, "Cancel");
        initLabel(PROP_PREVBUTTONTEXT, "Prev");
        initLabel(PROP_NEXTBUTTONTEXT, "Next");
        initLabel(PROP_FINISHBUTTONTEXT, "Finish");
        initIcon(PROP_CANCELBUTTONICON);
        initIcon(PROP_PREVBUTTONICON);
        initIcon(PROP_NEXTBUTTONICON);
        initIcon(PROP_FINISHBUTTONICON);
        initComponents();
        for( WizardPanel aPanel : model ) {
            this.cardPanel.add(aPanel.getComponent(), aPanel.getId());
        }
        addWindowListener(new WindowHandler());
    }

    //=========================================================================
    // GETTERS AND SETTERS
    //=========================================================================

    /**
     * Get the value of cancelButtonText
     * @return the value of cancelButtonText
     */
    public String getCancelButtonText() {
        return buttonText.get(PROP_CANCELBUTTONTEXT);
    }

    /**
     * Set the value of cancelButtonText
     * @param cancelButtonText new value of cancelButtonText
     */
    public void setCancelButtonText(String cancelButtonText) {
        String oldCancelButtonText = getCancelButtonText();
        buttonText.put(PROP_CANCELBUTTONTEXT, cancelButtonText);
        propertyChangeSupport.firePropertyChange(PROP_CANCELBUTTONTEXT, oldCancelButtonText, cancelButtonText);
    }

    /**
     * Get the value of cancelButtonIcon
     * @return the value of cancelButtonIcon
     */
    public Icon getCancelButtonIcon() {
        return buttonIcons.get(PROP_CANCELBUTTONICON);
    }

    /**
     * Set the value of cancelButtonIcon
     * @param cancelButtonIcon new value of cancelButtonIcon
     */
    public void setCancelButtonIcon(Icon cancelButtonIcon) {
        Icon oldCancelButtonIcon = getCancelButtonIcon();
        buttonIcons.put(PROP_CANCELBUTTONICON, cancelButtonIcon);
        propertyChangeSupport.firePropertyChange(PROP_CANCELBUTTONICON, oldCancelButtonIcon, cancelButtonIcon);
    }

    /**
     * Get the value of nextButtonText
     * @return the value of nextButtonText
     */
    public String getNextButtonText() {
        return buttonText.get(PROP_NEXTBUTTONTEXT);
    }

    /**
     * Set the value of nextButtonText
     * @param nextButtonText new value of nextButtonText
     */
    public void setNextButtonText(String nextButtonText) {
        String oldNextButtonText = getNextButtonText();
        buttonText.put(PROP_NEXTBUTTONTEXT, nextButtonText);
        propertyChangeSupport.firePropertyChange(PROP_NEXTBUTTONTEXT, oldNextButtonText, nextButtonText);
    }

    /**
     * Get the value of nextButtonIcon
     * @return the value of nextButtonIcon
     */
    public Icon getNextButtonIcon() {
        return buttonIcons.get(PROP_NEXTBUTTONICON);
    }

    /**
     * Set the value of nextButtonIcon
     * @param nextButtonIcon new value of nextButtonIcon
     */
    public void setNextButtonIcon(Icon nextButtonIcon) {
        Icon oldNextButtonIcon = getNextButtonIcon();
        buttonIcons.put(PROP_NEXTBUTTONICON, nextButtonIcon);
        propertyChangeSupport.firePropertyChange(PROP_NEXTBUTTONICON, oldNextButtonIcon, nextButtonIcon);
    }

    /**
     * Get the value of prevButtonText
     * @return the value of prevButtonText
     */
    public String getPrevButtonText() {
        return buttonText.get(PROP_PREVBUTTONTEXT);
    }

    /**
     * Set the value of prevButtonText
     * @param prevButtonText new value of prevButtonText
     */
    public void setPrevButtonText(String prevButtonText) {
        String oldPrevButtonText = getPrevButtonText();
        buttonText.put(PROP_PREVBUTTONTEXT, prevButtonText);
        propertyChangeSupport.firePropertyChange(PROP_PREVBUTTONTEXT, oldPrevButtonText, prevButtonText);
    }

    /**
     * Get the value of prevButtonIcon
     * @return the value of prevButtonIcon
     */
    public Icon getPrevButtonIcon() {
        return buttonIcons.get(PROP_PREVBUTTONICON);
    }

    /**
     * Set the value of prevButtonIcon
     * @param prevButtonIcon new value of prevButtonIcon
     */
    public void setPrevButtonIcon(Icon prevButtonIcon) {
        Icon oldPrevButtonIcon = getPrevButtonIcon();
        buttonIcons.put(PROP_PREVBUTTONICON, prevButtonIcon);
        propertyChangeSupport.firePropertyChange(PROP_PREVBUTTONICON, oldPrevButtonIcon, prevButtonIcon);
    }

    /**
     * Get the value of finishButtonIcon
     * @return the value of finishButtonIcon
     */
    public Icon getFinishButtonIcon() {
        return buttonIcons.get(PROP_FINISHBUTTONICON);
    }

    /**
     * Set the value of finishButtonIcon
     * @param finishButtonIcon new value of finishButtonIcon
     */
    public void setFinishButtonIcon(Icon finishButtonIcon) {
        Icon oldFinishButtonIcon = getFinishButtonIcon();
        buttonIcons.put(PROP_FINISHBUTTONICON, finishButtonIcon);
        propertyChangeSupport.firePropertyChange(PROP_FINISHBUTTONICON, oldFinishButtonIcon, finishButtonIcon);
    }

    /**
     * Get the value of finishButtonText
     *
     * @return the value of finishButtonText
     */
    public String getFinishButtonText() {
        return buttonText.get(PROP_FINISHBUTTONTEXT);
    }

    /**
     * Set the value of finishButtonText
     *
     * @param finishButtonText new value of finishButtonText
     */
    public void setFinishButtonText(String finishButtonText) {
        String oldFinishButtonText = getFinishButtonText();
        buttonText.put(PROP_FINISHBUTTONTEXT, finishButtonText);
        propertyChangeSupport.firePropertyChange(PROP_FINISHBUTTONTEXT, oldFinishButtonText, finishButtonText);
    }

    //=========================================================================
    // INTERFACE METHODS
    //=========================================================================

    /**
     * Get the dialog in which the wizard is displayed
     * @return Dialog
     */
    public JDialog getDialog() {
        return this;
    }

    /**
     * Get the model associated with this wizard
     * @return Wizard model
     */
    public WizardModel getModel() {
        return model;
    }

    /**
     * Show the dialog
     * @return return status
     */
    public int showDialog() {
        setModal(true);
        pack();
        setVisible(true);

        return returnStatus;
    }

    /**
     * Get return status
     * @return Status to indicate if user exited out of dialog with a finish or
     * cancel status
     */
    public int getReturnStatus() {
        return returnStatus;
    }

    /**
     * Take note of a state change
     * @param e
     */
    public void stateChanged(ChangeEvent e) {
        WizardPanel currentPanel = model.getCurrentPanel();
        if ( e.getSource().equals(currentPanel) ) {
            // Re-evaluate button status
            setButtonStates();
        }
    }

    /**
     * Indicates that a wizard panel was added to the model
     * @param event Model event
     */
    public void wizardPanelAdded(WizardModelEvent event) {
        WizardPanel panel = event.getPanel();
        Component component = panel.getComponent();
        cardPanel.add(component, panel.getId());
    }

    /**
     * Indicates that a wizard panel was remove from the model
     * @param event Model event
     */
    public void wizardPanelRemoved(WizardModelEvent event) {
        WizardPanel panel = event.getPanel();
        Component component = panel.getComponent();

        // If the panel being removed was the current panel, then we have to
        // punt by resetting. Unfortunately, we can't ask the model if this is
        // the current panel because the model has already removed it.
        // TODO Find out if component is the current panel

        cardPanel.remove(component);
    }

    /**
     * Receive notification that the wizard model is about to change
     * @param event Model event
     */
    public void modelIsChanging(WizardModelEvent event) {
        // Notify current panel that it is about to be hidden
        hidePanel(event.getModel().getCurrentPanel());
    }

    /**
     * Receive notification that the wizard model has changed
     * @param event Model event
     */
    public void modelChanged(WizardModelEvent event) {
        // Now that model has changed, update UI and notify new panel
        showPanel(event.getModel().getCurrentPanel());
    }


    //=========================================================================
    // OVERRIDE METHODS
    //=========================================================================

    @Override
    public void setVisible(boolean on) {
        WizardPanel currentPanel = model.getCurrentPanel();
        if ( on ) {
            model.reset();
            showPanel(currentPanel);
        }
        else {
            hidePanel(currentPanel);
        }
        super.setVisible(on);
    }

    //=========================================================================
    // PRIVATE and PROTECTED METHODS
    //=========================================================================

    /**
     * Set button states
     */
    protected void setButtonStates() {
        WizardPanel currentPanel = model.getCurrentPanel();
        if ( currentPanel == null ) {
            return;
        }
        prevButton.setEnabled(currentPanel.canGoBack() &&
                model.hasPreviousPanel());
        nextButton.setEnabled(currentPanel.canGoForward());
        finishButton.setEnabled(currentPanel.canFinish());
    }

    /**
     * Hide the specified panel. This ought to be the current panel.
     * @param panel Wizard panel that is about to be hidden
     */
    protected void hidePanel(WizardPanel panel) {

        // Notify panel
        panel.aboutToHide(this, returnStatus != STATUS_CANCEL);

        // Remove listener from outgoing panel
        panel.removeChangeListener(this);

        // Remove outgoing panel componenet from dialog
        cardPanel.remove(panel.getComponent());
    }

    /**
     * Notify a panel that it is about to be displayed
     * @param newPanel New panel
     */
    protected void showPanel(WizardPanel newPanel) {

        // Add change listener to new panel
        newPanel.addChangeListener(this);

        // Notify new panel
        newPanel.aboutToDisplay(this);

        // Make the switch
        ((CardLayout) cardPanel.getLayout()).show(cardPanel, newPanel.getId());

        // Update text & icons on buttons
        setButtonStates();
    }

    /**
     * Close dialog. This sets the return code and hides the dialog. It does
     * not dispose the dialog.
     * @param status Return status to set
     */
    protected void close(int status) {
        this.returnStatus = status;
        setVisible(false);
    }

    /**
     * Initialize a label text string from the resource bundle
     * @param propName Property name of the label text to initialize. This is
     * also the key in the resource bundle that is used to lookup the value.
     * @param defaultValue Value to use if the resource bundle does not contain
     * the specified <tt>propName</tt> as a key.
     */
    protected void initLabel(String propName, String defaultValue) {
        String value;
        try {
            value = uiText.getString(propName);
        }
        catch(MissingResourceException e) {
            // Use default value
            value = defaultValue;
        }
        buttonText.put(propName, value);
    }

    /**
     * Initialize a label icon from the resource bundle. If the specified key
     * does not exist in the resource bundle, then the button associated with
     * <tt>propName</tt> will not be initialized with an icon.
     * @param propName Property name of the label icon to initialize. This is
     * also the key in the resource bundle that is used to lookup the value.
     */
    protected void initIcon(String propName) {
        String iconLocation = uiText.getString(propName);
        buttonIcons.put(propName, new ImageIcon(getClass().getResource(iconLocation)));
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        cardPanel = new javax.swing.JPanel();
        buttonPanel = new javax.swing.JPanel();
        separator = new javax.swing.JSeparator();
        buttonBox = new javax.swing.JPanel();
        prevButton = new javax.swing.JButton();
        nextButton = new javax.swing.JButton();
        finishButton = new javax.swing.JButton();
        cancelButton = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        cardPanel.setLayout(new java.awt.CardLayout());
        getContentPane().add(cardPanel, java.awt.BorderLayout.CENTER);

        buttonPanel.setLayout(new java.awt.BorderLayout());
        buttonPanel.add(separator, java.awt.BorderLayout.NORTH);

        buttonBox.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 10, 5, 10));
        buttonBox.setLayout(new java.awt.GridBagLayout());

        prevButton.setIcon(getPrevButtonIcon());
        prevButton.setText(getPrevButtonText());
        prevButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                prevButtonClicked(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 10);
        buttonBox.add(prevButton, gridBagConstraints);

        nextButton.setIcon(getNextButtonIcon());
        nextButton.setText(getNextButtonText());
        nextButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                nextButtonClicked(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 10);
        buttonBox.add(nextButton, gridBagConstraints);

        finishButton.setIcon(getFinishButtonIcon());
        finishButton.setText(getFinishButtonText());
        finishButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                finishButtonClicked(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 10);
        buttonBox.add(finishButton, gridBagConstraints);

        cancelButton.setIcon(getCancelButtonIcon());
        cancelButton.setText(getCancelButtonText());
        cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cancelButtonClicked(evt);
            }
        });
        buttonBox.add(cancelButton, new java.awt.GridBagConstraints());

        buttonPanel.add(buttonBox, java.awt.BorderLayout.CENTER);

        getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void prevButtonClicked(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevButtonClicked
        model.goToPreviousPanel();
}//GEN-LAST:event_prevButtonClicked

    private void nextButtonClicked(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextButtonClicked
        model.goToNextPanel(model.getCurrentPanel().getNextPanelId());
}//GEN-LAST:event_nextButtonClicked

    private void cancelButtonClicked(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonClicked
        // Close dialog
        close(STATUS_CANCEL);
}//GEN-LAST:event_cancelButtonClicked

    private void finishButtonClicked(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_finishButtonClicked
        close(STATUS_FINISH);
    }//GEN-LAST:event_finishButtonClicked

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel buttonBox;
    private javax.swing.JPanel buttonPanel;
    private javax.swing.JButton cancelButton;
    private javax.swing.JPanel cardPanel;
    private javax.swing.JButton finishButton;
    private javax.swing.JButton nextButton;
    private javax.swing.JButton prevButton;
    private javax.swing.JSeparator separator;
    // End of variables declaration//GEN-END:variables

    private class WindowHandler implements WindowListener {

        public void windowActivated(WindowEvent e) {
        }

        public void windowClosed(WindowEvent e) {
        }

        public void windowClosing(WindowEvent e) {
            close(STATUS_CANCEL);
        }

        public void windowDeactivated(WindowEvent e) {
        }

        public void windowDeiconified(WindowEvent e) {
        }

        public void windowIconified(WindowEvent e) {
        }

        public void windowOpened(WindowEvent e) {
        }
    }
}
